﻿Set-PSDebug -strict

#region Helper Functions
function Get-ScriptDir
{
	return "E:\WindowsPowerShell\google\trunk\vStorageDRS"
}
function Get-CacheDir
{
	return ( Join-Path ( Get-ScriptDir ) "Cache" )
}
#endregion
#region VI Connection functions
function New-VIConnection
{
	param `
	(
		[string] $server,
		[System.Management.Automation.PSCredential] $credential
	)
	
	$new = New-Object System.Management.Automation.PSObject `
	| Add-Member -MemberType ScriptMethod -Name ToString -Value {("{0} object -> {1}" -f $this.PSObject.TypeNames[0], $this.Server)} -Force -PassThru `
	| Add-Member -MemberType NoteProperty -Name Server -Value $server -PassThru `
	| Add-Member -MemberType NoteProperty -Name Credential -Value $credential -PassThru `
	| Add-Member -MemberType NoteProperty -Name Connection -Value $null -PassThru `
	| Add-Member -MemberType NoteProperty -Name Connected -Value $false -PassThru `

	$new.PSObject.TypeNames.Clear()
	$new.PSObject.TypeNames.Insert(0,'VIConnection')
	return $new
}

function Connect-VIConnection
{
	param `
	(
		$con
	)
	
	if ( -not $con.Connected )
	{
		$con.Connection = Connect-VIServer $con.Server -Credential $con.Credential
		$con.Connection | Add-Member -MemberType NoteProperty -Name ServiceInstance -Value (Get-View -Id ServiceInstance -Server $con.Connection)
		$con.Connected = $true
	}
	return $con
}

function Disconnect-VIConnection
{
	param `
	(
		$con
	)
	
	if ( $con.Connected )
	{
		$con.Connection = Disconnect-VIServer -Server $con.Server -Confirm:$false
		$con.Connection = $null
		Remove-Variable $DefaultVIServer
		$this.Connected = $false
	}
	return $con
}

function Get-ViConnection
{
	param `
	(
		$con
	)
	
	if ( -not $con.Connected )
	{
		$con  = Connect-VIConnection -con $con
	}
	return $con.Connection
}
#endregion
#region DataStore Functions
function Get-DataStoresOfVmHost
{
	param `
	(
		$con,
		$vmHost
	)
	$cacheFile = ( Join-Path ( Get-CacheDir ) ( $vmHost.Name + ".dataStoresOfVmHost.xml" ) )
	$ds = $null
	if ( -not ( Test-Path $cacheFile ) )
	{
		$ds = Get-Datastore -VMHost $vmHost
		$ds | Export-Clixml $cacheFile
	}
	else
	{
		$ds = Import-Clixml $cacheFile
	}
	return $ds
}
function Get-DataStoresInFolder
{
	param `
	(
		$con,
		[string] $folder
	)
	$cacheFile = ( Join-Path ( Get-CacheDir ) ( $folder + ".dataStoresInFolder.xml" ) )
	if ( -not ( Test-Path $cacheFile ) )
	{
		$f = Get-Folder $folder -Server ( Get-VIConnection -con $con )
		$vmhosts = Get-VMHost -location $f
		$vmhost = $vmhosts | Select -First 1
		$ds = Get-DataStoresOfVmHost -vmhost $vmhost -con $con
		$ds | Export-Clixml $cacheFile
	}
	else
	{
		$ds = Import-Clixml $cacheFile
	}
	return $ds
}
#endregion
#region VM functions
function Get-VmsInFolder
{
	param `
	(
		$con,
		$folder
	)
	$cacheFile = ( Join-Path ( Get-CacheDir ) ( $folder + ".VmsInFolder.xml" ) )
	$vms = $null
	if ( -not ( Test-Path $cacheFile ) )
	{
		$f = Get-Folder $folder -Server ( Get-VIConnection -con $con )
		$vms = Get-VM -Location $f
		$vms | Export-Clixml $cacheFile
	}
	else
	{
		$vms = Import-Clixml $cacheFile
	}
	return $vms
}
function Get-VmHdds
{
	param `
	(
		$con,
		$vm
	)
	$cacheFile = ( Join-Path ( Get-CacheDir ) ( $vm.Name + ".VmsHdds.xml" ) )
	$hdds = $null
	if ( -not ( Test-Path $cacheFile ) )
	{
		$hdds = Get-HardDisk -VM $vm
		$hdds | Export-Clixml $cacheFile
	}
	else
	{
		$hdds = Import-Clixml $cacheFile
	}
	return $hdds
}
function Get-ViewOfVm
{
	param `
	(
		$vm,
		$con
	)
	$cacheFile = ( Join-Path ( Get-CacheDir ) ( $vm.Name + ".view.xml" ) )
	$view = $null
	if ( -not ( Test-Path $cacheFile ) )
	{
		$view = Get-View $vm.Id -Server ( Get-VIConnection -con $con )
		$view | Export-Clixml $cacheFile
	}
	else
	{
		$view = Import-Clixml $cacheFile
	}
	return $view
}
function Get-VIStat
{
	param `
	(
		$entity = $(Throw "This is a required parameter."),
		$start,
		$finish,
		[string]$stat,
		[string]$instance = "",
		[string]$interval,	
		[int]$maxsamples,
		$con
	)
	
	$serviceInstance = Get-View ServiceInstance -Server ( Get-VIConnection -con $con)
	$perfMgr = Get-View $serviceInstance.content.perfManager -Server ( Get-VIConnection -con $con)
	
	# Create performance counter hashtable
	$pcTable = New-Object Hashtable 
	foreach ($pC in $perfMgr.PerfCounter)
	{
		if ($pC.Level -ne 99)
		{
			$pctable.Add(($pC.GroupInfo.Key + "." + $pC.NameInfo.Key + "." + $pC.RollupType),$pC.Key)
		}
	}
	
	# Test if entity is valid
	if(!(("HostSystem", "VirtualMachine", "ComputeResource", "ResourcePool") -contains $entity.gettype().Name))
	{
		Throw "-Entity parameters should be of type HostSystem , VirtualMachine, ComputeResource or ResourcePool"
	}
	
	# Test if start is valid
	if ($start -ne $null -and $start -ne "")
	{
		if($start.gettype().name -ne "DateTime")
		{
			Throw "-Start parameter should be a DateTime value"
		}
	}
	
	# Test if finish is valid
	if ($finish -ne $null -and $finish -ne "")
	{
		if ($finish.gettype().name -ne "DateTime")
		{
			Throw "-Start parameter should be a DateTime value"
		}
	}
	
	# Test start-finish interval
	if ($start -ne "" -and $finish -ne "" -and $start -ge  $finish)
	{
		Throw "-Start time should 'older' than -Finish time."
	}
	
	# Test if stat is valid
	$statarray = $stat.Split(" ")
	$unitarray = @()
	foreach ($st in $statarray)
	{
		if ($pcTable[$st] -eq $null)
		{
			Throw "-Stat parameter $st is invalid."
		}
		$pcInfo = $perfMgr.QueryPerfCounter($pcTable[$st])
		$unitarray += $pcInfo[0].UnitInfo.Key
	
		# Test if instance is valid
		$metricId = $perfMgr.QueryAvailablePerfMetric($entity.MoRef, $null, $null, $null)
		$found = $false
		$validInstances =@()
		foreach ($metric in $metricId)
		{
			if ($metric.CounterId -eq $pcTable[$st])
			{
				if($metric.Instance -eq "")
				{
					$cInstance = '""'
				}
				else
				{
					$cInstance = $metric.Instance
				}
				$validInstances += $cInstance
				if ($instance -eq $metric.Instance)
				{
					$found = $true
				}
			}
		}
		if (!$found)
		{
			#Write-Host "-Instance parameter invalid for requested stat: $st.`nValid values are: $validInstances"
		}
	}
	
	# Test for a valid $interval
	if ($interval.ToString().Split(" ").count -gt 1)
	{
		Throw "Only 1 interval allowed."
	}
	
	switch ($interval)
	{
		"RT" {$numinterval = 20}
		"HI1" {$numinterval = $perfMgr.HistoricalInterval[0].SamplingPeriod}
		"HI2" {$numinterval = $perfMgr.HistoricalInterval[1].SamplingPeriod}
		"HI3" {$numinterval = $perfMgr.HistoricalInterval[2].SamplingPeriod}
		"HI4" {$numinterval = $perfMgr.HistoricalInterval[3].SamplingPeriod}
		Default {
			Throw "-Interval parameter invalid. `nValid values are: $validInterval"
		}
	}
	
	# Test if $maxsamples is valid (only for real-time !)
	if ($maxsamples -ne "")
	{
		Throw "-Maxsamples parameter is only valid for real time (-Interval 20) statistics"
	}
	
	$PQSpec = New-Object VMware.Vim.PerfQuerySpec
	$PQSpec.entity = $entity.MoRef
	$PQSpec.Format = "normal"
	$PQSpec.IntervalId =  $numinterval
	$PQSpec.MetricId = @()
	foreach ($st in $statarray)
	{
		$PMId = New-Object VMware.Vim.PerfMetricId
		$PMId.counterId = $pcTable[$st]
		if ($instance -ne $null)
		{
			$PMId.instance = $instance
		}
		$PQSpec.MetricId += $PMId
	}
	$PQSpec.StartTime = $start
	$PQSpec.EndTime = $finish
	
	$stats = $perfMgr.QueryPerf($PQSpec)
	
	# No data available (yet)
	
	if ($stats[0].Value -eq $null) {return $null}
	
	# Extract data to custom object and return as array
	$data = @()
	for($i=0; $i -lt $stats[0].SampleInfo.Count; $i++)
	{
		for($j=0; $j -lt $statarray.Count; $j++)
		{
			$row = "" | Select CounterId, CounterName, Instance, Timestamp, Interval, Value, Unit
			$row.CounterId = $stats[0].Value[$j].Id.CounterId
			$row.CounterName = $statarray[$j]
			$row.Instance = $stats[0].Value[$j].Id.Instance
			$row.Timestamp = $stats[0].SampleInfo[$i].Timestamp
			$row.Interval = $stats[0].SampleInfo[$i].Interval
			$row.Value = $stats[0].Value[$j].Value[$i]
			$row.Unit = $unitarray[$j]
			$data += $row
		}
	}
	$data
}
function Get-IOAverage
{
	param `
	(
		$view,
		$con
	)
	$cacheFile = ( Join-Path ( Get-CacheDir ) ( $view.Name + ".ioAverage.xml" ) )
	$ioAverage = $null
	if ( -not ( Test-Path $cacheFile ) )
	{
		$ioStat = Get-IOStat -view $view -con $con
		$ioAverage = ( $ioStat | Measure-Object -Property Value -Sum ).Sum / 3360
		$ioAverage | Export-Clixml $cacheFile
	}
	else
	{
		$ioAverage = Import-Clixml $cacheFile
	}
	return $ioAverage
}
function Get-IOStat
{
	param `
	(
		$view,
		$con
	)
	$cacheFile = ( Join-Path ( Get-CacheDir ) ( $view.Name + ".ioStat.xml" ) )
	$ioStat = $null
	if ( -not ( Test-Path $cacheFile ) )
	{
		$ioStat = Get-VIStat -entity $view -start (Get-Date).AddDays(-7) -finish (Get-Date) -stat disk.usage.average -interval HI1 -con $con
		$ioStat | Export-Clixml $cacheFile
	}
	else
	{
		$ioStat = Import-Clixml $cacheFile
	}
	return $ioStat
}
#endregion
#region SDRS Vm Functions
function New-SDRSVm
{
	param `
	(
		[string] $name,
		[float] $capacityGB = 0,
		[string] $currentLun = $null,
		$ioAverage
	)
	
	$new = New-Object System.Management.Automation.PSObject `
	| Add-Member -MemberType ScriptMethod -Name ToString -Value {("{0} object -> {1}" -f $this.PSObject.TypeNames[0], $this.Name)} -Force -PassThru `
	| Add-Member -MemberType NoteProperty -Name Name -Value $name -PassThru `
	| Add-Member -MemberType NoteProperty -Name CapacityGB -Value $capacityGB -PassThru `
	| Add-Member -MemberType NoteProperty -Name CurrentLun -Value $currentLun -PassThru `
	| Add-Member -MemberType NoteProperty -Name IOAverage -Value $ioAverage -PassThru `
	| Add-Member -MemberType NoteProperty -Name IOType -Value $null -PassThru `
	| Add-Member -MemberType NoteProperty -Name SDRSPrio -Value 0 -PassThru `

	$new.PSObject.TypeNames.Clear()
	$new.PSObject.TypeNames.Insert(0,'SDRSVm')
	return $new
}
#endregion
#region SDRS Lun Functions
function New-SDRSLun
{
	param `
	(
		[string] $name,
		[float] $capacityGB = 0
	)
	
	$new = New-Object System.Management.Automation.PSObject `
	| Add-Member -MemberType ScriptMethod -Name ToString -Value {("{0} object -> {1}" -f $this.PSObject.TypeNames[0], $this.Name)} -Force -PassThru `
	| Add-Member -MemberType NoteProperty -Name Name -Value $name -PassThru `
	| Add-Member -MemberType NoteProperty -Name Vms -Value @{} -PassThru `
	| Add-Member -MemberType NoteProperty -Name CapacityGB -Value $capacityGB -PassThru `
	| Add-Member -MemberType NoteProperty -Name SDRS -Value $true -PassThru `

	$new.PSObject.TypeNames.Clear()
	$new.PSObject.TypeNames.Insert(0,'SDRSLun')
	return $new
}
function Add-SDRSVmToLun
{
	param `
	(
		$vm,
		$lun
	)
	$lun.Vms[$vm.Name] = $vm
}
function Print-SDRSLun
{
	param `
	(
		$lun
	)
	Write-Host ( "`nDataStore: {0}" -f $lun.Name )
	Write-Host ( "Capacity:  {0} GB" -f [Math]::Round($lun.CapacityGB, 2) )
	Write-Host ( "Used:      {0} GB" -f [Math]::Round((Get-SDRSLunUsedGB -lun $lun), 2 ) )
	Write-Host ( "Free:      {0} GB" -f [Math]::Round((Get-SDRSLunFreeGB -lun $lun), 2 ) )
	Write-Host ( "Burden:    {0} KB/s" -f [Math]::Round((Get-SDRSLunBurden -lun $lun), 2 ) )
	Write-Host "-------------------------------------------------------------------------------"
	foreach ( $type in @( "hot", "medium", "low", $null ) )
	{
		$vms = $lun.Vms.Values | where { $_.IOType -eq $type } | Sort Name
		if ( $vms -ne $null )
		{
			if ( $type -ne $null )
			{
				$type += "IoVms"
			}
			else
			{
				$type = "Unclassified Vms"
			}
			Write-Host ( "{0}:" -f $type )
			Write-Host "-------------------------------------------------------------------------------"
			foreach ( $vm in  $vms )
			{
				Write-Host -NoNewline ( "`tVm: {0, -25}, Capacity: {1, 6} GB, Burden: {2, 6} KB/s" -f $vm.Name, [Math]::Round($vm.CapacityGB, 2 ), [Math]::Round($vm.IOAverage, 2 ) )
				if ( $vm.CurrentLun -ne $lun.Name )
				{
					Write-Host -ForegroundColor Red ( "  SVMotion from {0}" -f $vm.CurrentLun)
				}
				else
				{
					Write-Host " "
				}
			}
			Write-Host "-------------------------------------------------------------------------------"
		}
	}
}
function Get-SDRSLunUsedGB
{
	param `
	(
		$lun
	)
	$i = 0
	foreach ( $vm in $lun.Vms.Values )
	{
		$i += $vm.CapacityGB
	}
	return $i
}
function Get-SDRSLunFreeGB
{
	param `
	(
		$lun
	)
	return ( $lun.CapacityGB - ( Get-SDRSLunUsedGB -lun $lun ) )
}
function Get-SDRSLunBurden
{
	param `
	(
		$lun
	)
	$i = 0
	foreach ( $vm in $lun.Vms.Values )
	{
		$i += $vm.IOAverage
	}
	return $i
}
#endregion
#region SDRS Layout functions
function New-SDRSLayout
{
	param `
	(
		[string] $name,
		$storageCats,
		$maxVmsPerLun,
		$maxHotIOVmsPerLun,
		$maxMediumIOVmsPerLun,
		$minFreeGBPerLun
	)
	
	$new = New-Object System.Management.Automation.PSObject `
	| Add-Member -MemberType ScriptMethod -Name ToString -Value {("{0} object -> {1}" -f $this.PSObject.TypeNames[0], $this.Name)} -Force -PassThru `
	| Add-Member -MemberType NoteProperty -Name Name -Value $name -PassThru `
	| Add-Member -MemberType NoteProperty -Name Luns -Value @{} -PassThru `
	| Add-Member -MemberType NoteProperty -Name VmOnLunIndex -Value @{} -PassThru `
	| Add-Member -MemberType NoteProperty -Name StorageCats -Value $storageCats -PassThru `
	| Add-Member -MemberType NoteProperty -Name MaxVmsPerLun -Value $maxVmsPerLun -PassThru `
	| Add-Member -MemberType NoteProperty -Name MaxHotIOVmsPerLun -Value $maxHotIOVmsPerLun -PassThru `
	| Add-Member -MemberType NoteProperty -Name MaxMediumIOVmsPerLun -Value $maxMediumIOVmsPerLun -PassThru `
	| Add-Member -MemberType NoteProperty -Name MinFreeGBPerLun -Value $minFreeGBPerLun -PassThru `

	$new.PSObject.TypeNames.Clear()
	$new.PSObject.TypeNames.Insert(0,'SDRSLayout')
	return $new
}
function Add-SDRSLunToLayout
{
	param `
	(
		$lun,
		$layout
	)
	$layout.Luns[$lun.Name] = $lun
}
function Get-SDRSLunByName
{
	param `
	(
		$lunName,
		$layout
	)
	if ( -not ( $layout.Luns.Keys -contains $lunName ) )
	{
		Throw ( "Can not find Index of Lun: {0}`n" -f $lunName)
	}
	return $layout.Luns[$lunName]
}
function Get-SDRSVmByName
{
	param `
	(
		$vmName,
		$layout
	)
	if ( -not ($layout.VmOnlunIndex.Keys -contains $vmName) )
	{
		Throw ("Can not find Vm {0} in VmOnLunIndex" -f $vmName )
	}
	return $layout.Luns[$layout.VmOnLunIndex[$vmName]].Vms[$vmName]
}
function Add-SDRSVmToLayout
{
	param `
	(
		$vm,
		$layout,
		$lun = $null
	)
	if ( $lun -eq $null )
	{
		$lun = Get-SDRSLunByName -layout $layout -lunName $vm.CurrentLun
	}
	Add-SDRSVmToLun -vm $vm -lun $lun
	$layout.VmOnLunIndex[$vm.Name] = $lun.Name
}
function Add-CurrentDataStoresToLayout
{
	param `
	(
		$con,
		$layout,
		[string] $folder
	)
	Write-Host ("`nFetching Datastores in folder: {0}" -f $folder)
	$ds = Get-DataStoresInFolder -con $con -folder $folder
	Write-Host ("Datastores found: {0}`n" -f $ds.Count)
	foreach ( $d in $ds | Sort Name )
	{
		$lun = New-SDRSLun -name $d.Name -capacityGB $($d.CapacityMB / 1KB)
		Add-SDRSLunToLayout -lun $lun -layout $layout
		Write-Host -NoNewline ("{0}, " -f $lun.Name)
	}
	Write-Host "`n"
}
function Add-CurrentVmsToLayout
{
	param `
	(
		$con,
		$layout,
		[string] $folder
	)
	Write-Host ("`nFetching Vms in folder: {0}" -f $folder)
	$vms = Get-VmsInFolder -con $con -folder $folder
	Write-Host ("Vms found: {0}`n" -f $vms.Count)
	foreach ( $vm in $vms | Sort Name )
	{
		$hdds = Get-VmHdds -con $con -vm $vm
		$capacityGB = 0
		$storageName = ""
		foreach ( $hdd in $hdds )
		{
			if ( $hdd.DiskType.ToString() -eq "Flat" )
			{
				$capacityGB += ($hdd.CapacityKB / 1MB)
				$storageName = ($hdd.Filename.split())[0]
				$storageName  = $storageName -replace("\[", "")
				$storageName  = $storageName -replace("\]", "")
			}
		}
		
		$view = Get-ViewOfVm -vm $vm -con $con
		$ioAverage = Get-IOAverage -view $view -con $con
		
		
		
		$sdrsVm = New-SDRSVm -name $vm.Name -capacityGB $capacityGB -currentlun $storageName -ioAverage $ioAverage
		Add-SDRSVmToLayout -vm $sdrsVm -layout $layout
		Write-Host -NoNewline ("{0}, " -f $sdrsVm.Name)
	}
	Write-Host "`n"
}
function Get-CurrentLayout
{
	param `
	(
		$layout,
		[string] $folder
	)
	$con = New-VIConnection -Server msesxmgt02 -Credential (Import-PSCredential ham-vmbatch)
	Add-CurrentDataStoresToLayout -con $con -layout $layout -folder $folder
	Add-CurrentVmsToLayout -con $con -layout $layout -folder $folder
}
function Print-SDRSLayout
{
	param `
	(
		$layout = $input
	)
	foreach ( $lun in $layout.Luns.Values | Sort Name )
	{
		Print-SDRSLun -lun $lun
	}
}
function Print-SDRSLayoutByStorageCat
{
	param `
	(
		$layout = $input,
		$storageCat
	)
	foreach ( $lun in ( Get-SDRSLunsByStorageCat -layout $layout -storageCat $storageCat )| Sort Name )
	{
		Print-SDRSLun -lun $lun
	}
}
function Copy-Layout
{
	param `
	(
		$source = ( Throw "`$layout is a required parameter" ),
		$target = ( Throw "`$layout is a required parameter" ),
		$withVms = $false
	)
	
	foreach ( $lun in $source.Luns.Values )
	{
		$newLun = New-SDRSLun -name $lun.Name -capacityGB $lun.CapacityGB -storageCats $source.StorageCats
		Add-SDRSLunToLayout -lun $newLun -layout $target
		
		if ( $withVms -eq $true )
		{
			foreach ( $vm in $lun.Vms.Values )
			{
				$newVm = New-SDRSVm -name $vm.Name -capacityGB $vm.CapacityGB -currentlun $vm.Currentlun -ioAverage $vm.ioAverage
				Add-SDRSVmToLayout -vm $newVm -layout $target
			}
		}
	}
}
function Set-SDRSLayoutIOTypes
{
	param `
	(
		$layout = ( Throw "`$layout is a required parameter" )
	)
	
	foreach ( $cat in $layout.StorageCats )
	{
		$vms = Get-SDRSVmsByStorageCat -layout $layout -storageCat $cat | Sort IOAverage -Descending
		
		$lunCount = (Get-SDRSLunsByStorageCat -layout $layout -StorageCat $cat).Count
		$maxHotCount = $lunCount * $layout.MaxHotIOVmsPerLun
		$maxMediumCount = $maxHotCount + $lunCount * $layout.MaxMediumIOVmsPerLun
		
		$i = 0
		foreach ( $vm in $vms )
		{
			if ( $i -lt $maxHotCount )
			{
				$vm.IOType = "hot"
			}
#			elseif ( $i -lt $maxMediumCount )
#			{
#				$vm.IOType = "medium"
#			}
			else
			{
				$vm.IOType = "low"
			}
			$i++
		}
	}
}
function Get-SDRSLunsByStorageCat
{
	param `
	(
		$layout,
		$storageCat
	)
	return $layout.Luns.Values | where { $_.Name -match $storageCat.MatchString }
}
function Get-SDRSVmsByStorageCat
{
	param `
	(
		$layout,
		$storageCat
	)
	$array = @()
	$layout.Luns.Values | where { $_.Name -match $storageCat.MatchString } | % { $array += $_.Vms.Values }
	return $array
}
function Get-SDRSVmsByLayout
{
	param `
	(
		$layout
	)
	$array = @()
	$layout.Luns.Values | % { $array += $_.Vms.Values }
	return $array
}
function Get-SDRSLunsByBurden
{	
	param `
	(
		$layout,
		$storageCat,
		$descending = $true
	)
	$luns = Get-SDRSLunsByStorageCat -layout $layout -storageCat $storageCat | Select Name, @{ Name = "Burden"; Expression = { Get-SDRSLunBurden -lun $_ } } | Sort Burden, Name -Descending:$descending
	
	$outLuns = @()
	foreach ( $lun in $luns )
	{
		$outLuns += $layout.Luns[$lun.Name]
	}
	return $outLuns
}
function Distribute-SDRSHotIOVms
{
	param `
	(
		$source,
		$target
	)
	
	foreach ( $cat in $target.StorageCats )
	{
		$vms = Get-SDRSVmsByStorageCat -layout $source -storageCat $cat | where { $_.IOType -eq "hot" }
		
		foreach ( $vm in $vms )
		{
			$lunName = $vm.CurrentLun
			$lun = $target.Luns[$lunName]
			$hotVmsOnLun = @( $lun.Vms.Values | where { $_.IOType -eq "hot" } )
			if ( ($hotVmsOnLun -ne $null) -and ($hotVmsOnLun.Count -ge $target.MaxHotIOVmsPerLun) )
			{
				$lun = Get-SDRSLunsByBurden -layout $target -storageCat $cat -descending $false | where { (Get-SDRSLunFreeGB -lun $_) -gt ($vm.CapacityGB + $target.MinFreeGBPerLun) } | Select -First 1
			}
			if ( $vm.CurrentLun -ne $lun.Name )
			{
				Write-Host ("{0}: {1} -> {2}" -f $vm.Name, $vm.CurrentLun, $lun.Name )
			}
			else
			{
				Write-Host ("{0}: stays on {1}" -f $vm.Name, $vm.CurrentLun )
			}
			Add-SDRSVMToLayout -vm $vm -layout $target -lun $lun
		}
	}
}
function Distribute-SDRSMediumIOVms
{
	param `
	(
		$source,
		$target
	)
	
	foreach ( $cat in $target.StorageCats )
	{
		$vms = Get-SDRSVmsByStorageCat -layout $source -storageCat $cat | where { $_.IOType -eq "medium" } | Sort IOAverage -Descending
		
		foreach ( $vm in $vms )
		{
			$lun = Get-SDRSLunsByBurden -layout $target -storageCat $cat -descending $false `
				| where `
				{
					(
						(Get-SDRSLunFreeGB -lun $_) -gt ($vm.CapacityGB + $target.MinFreeGBPerLun) 
					) -and
					(
						( @($_.Vms.Keys).Count -lt $target.MaxVmsPerLun )
					)
				} `
				| Select -First 1
			if ( $lun -eq $null )
			{
				Write-Host -ForegroundColor Red  ("`nNo LUN has enough free space to take VM {0}" -f $vm.Name )
				$lun = Free-SDRSLeastBurdenLunInStorageCat -layout $target -storageCat $cat -capacityNeeded $vm.CapacityGB
			}
			if ( $vm.CurrentLun -ne $lun.Name )
			{
				Write-Host ("{0}: {1} -> {2}" -f $vm.Name, $vm.CurrentLun, $lun.Name )
			}
			else
			{
				Write-Host ("{0}: stays on {1}" -f $vm.Name, $vm.CurrentLun )
			}
			Add-SDRSVMToLayout -vm $vm -layout $target -lun $lun
		}
	}
}
function Distribute-SDRSLowIOVms
{
	param `
	(
		$source,
		$target
	)
	
	foreach ( $cat in $target.StorageCats )
	{
		$allVms = Get-SDRSVmsByStorageCat -layout $source -storageCat $cat
		$vms = $allVms | where { $_.IOType -eq "low" } | Sort IOAverage -Descending
		
		foreach ( $vm in $vms )
		{
			$lookup = "^" + $vm.Name -replace ( "\d+$", "\d+$" )
			$lookup += ""
			if ( $cat.SubCats -and @($allVms | where { $_.Name -match $lookup }).Count -gt 1 )
			{
			}
			else
			{
				$lun = Get-SDRSLunsByBurden -layout $target -storageCat $cat -descending $false `
					| where `
					{
						(
							(Get-SDRSLunFreeGB -lun $_) -gt ($vm.CapacityGB + $target.MinFreeGBPerLun) 
						) -and
						(
							( @($_.Vms.Keys).Count -lt $target.MaxVmsPerLun )
						)
					} `
					| Select -First 1
				if ( $lun -eq $null )
				{
					Write-Host -ForegroundColor Red  ("`nNo LUN has enough free space to take VM {0}" -f $vm.Name )
					$lun = Free-SDRSLeastBurdenLunInStorageCat -layout $target -storageCat $cat -capacityNeeded $vm.CapacityGB
				}
				if ( $vm.CurrentLun -ne $lun.Name )
				{
					Write-Host ("{0}: {1} -> {2}" -f $vm.Name, $vm.CurrentLun, $lun.Name )
				}
				else
				{
					Write-Host ("{0}: stays on {1}" -f $vm.Name, $vm.CurrentLun )
				}
				Add-SDRSVMToLayout -vm $vm -layout $target -lun $lun
			}
		}
	}
}
function Free-SDRSLeastBurdenLunInStorageCat
{
	param `
	(
		$layout,
		$storageCat,
		$capacityNeeded
	)
	$lun = Get-SDRSLunsByBurden -layout $target -storageCat $cat -descending $false | Select -First 1
	
	Write-Host -ForegroundColor Red ( "`nNeed to free least burden LUN {0} to get {1}GB free space`n" -f $lun.Name, $capacityNeeded)
	while ( ((Get-SDRSLunFreeGB -lun $lun) - $layout.MinFreeGBPerLun) -lt $capacityNeeded )
	{
		$foundSomething = $false
		$lowVms = $lun.Vms.Values | Where { $_.IOType -eq "low" }
		if ( $lowVms -ne $null )
		{
			$reassignVm = $lowVms | Sort CapacityGB -Descending | Select -First 1
			$newLun = Get-SDRSLunsByBurden -layout $target -storageCat $cat -descending $false  | where { (($_.Name -ne $lun.Name) -and (($_.CapacityGB - $layout.MinFreeGBPerLun) -gt $reassignVM.CapacityGB) -and (@($_.Vms.Keys).Count -lt $layout.MaxVmsPerLun)) } | Select -First 1
			if ( $newLun -eq $null )
			{
				Throw ("Unable to relocate Vm {0} with CapacityGB {1}. No Lun has enough free space" -f $reassignVM.Name, $reassignVM.CapacityGB)
			}
			Write-Host ("{0}: {1} -> {2}" -f $reassignVM.Name, $lun.Name, $newLun.Name )
			$lun.Vms.Remove($reassignVm.Name)
			Add-SDRSVmToLayout -layout $layout -vm $reassignVm -lun $newLun
			$foundSomething = $true
			continue
		}
		$mediumVms = $lun.Vms.Values | Where { $_.IOType -eq "medium" }
		if ( $mediumVms -ne $null )
		{
			$reassignVm = $mediumVms | Sort CapacityGB -Descending | Select -First 1
			$newLun = Get-SDRSLunsByBurden -layout $target -storageCat $cat -descending $false  | where { (($_.Name -ne $lun.Name) -and (($_.CapacityGB - $layout.MinFreeGBPerLun) -gt $reassignVM.CapacityGB) -and (@($_.Vms.Keys).Count -lt $layout.MaxVmsPerLun)) } | Select -First 1
			if ( $newLun -eq $null )
			{
				Throw ("Unable to relocate Vm {0} with CapacityGB {1}. No Lun has enough free space" -f $reassignVM.Name, $reassignVM.CapacityGB)
			}
			Write-Host ("{0}: {1} -> {2}" -f $reassignVM.Name, $lun.Name, $newLun.Name )
			$lun.Vms.Remove($reassignVm.Name)
			Add-SDRSVmToLayout -layout $layout -vm $reassignVm -lun $newLun
			$foundSomething = $true
			continue
		}
		$hotVms = $lun.Vms.Values | Where { $_.IOType -eq "hot" }
		if ( $hotVms -ne $null )
		{
			$reassignVm = $hotVms | Sort CapacityGB -Descending | Select -First 1
			$newLun = Get-SDRSLunsByBurden -layout $target -storageCat $cat -descending $false  | where { (($_.Name -ne $lun.Name) -and (($_.CapacityGB - $layout.MinFreeGBPerLun) -gt $reassignVM.CapacityGB) -and (@($_.Vms.Keys).Count -lt $layout.MaxVmsPerLun)) } | Select -First 1
			if ( $newLun -eq $null )
			{
				Throw ("Unable to relocate Vm {0} with CapacityGB {1}. No Lun has enough free space" -f $reassignVM.Name, $reassignVM.CapacityGB)
			}
			Write-Host ("{0}: {1} -> {2}" -f $reassignVM.Name, $lun.Name, $newLun.Name )
			$lun.Vms.Remove($reassignVm.Name)
			Add-SDRSVmToLayout -layout $layout -vm $reassignVm -lun $newLun
			$foundSomething = $true
			continue
		}
		if ( -not $foundSomething )
		{
			Throw ( "No LUN has not enough capacity to get {0}GB free space" -f $capacityNeeded)
		}
	}
	Write-Host -ForegroundColor Green ("`nLeast Burden LUN freed`n")
	return $lun
}
function Optimize-SDRSLayout
{
	param `
	(
		$source,
		$target
	)
	Distribute-SDRSHotIOVms -source $source -target $target
	#Distribute-SDRSMediumIOVms -source $source -target $target
	Distribute-SDRSLowIOVms -source $source -target $target
	
	$moves = 0
	foreach ( $lun in $target.Luns.Values )
	{
		foreach ( $vm in $lun.Vms.Values )
		{
			if ( $lun.Name -ne $vm.CurrentLun )
			{
				$moves++
			}
		}
	}
	Write-Host ( "`nNew Layout needs {0} SVMotions" -f $moves)
}
#endregion
#region SDRS Storage Category Functions
function New-SDRSStorageCat
{
	param `
	(
		[string] $name,
		[string] $matchString
	)
	
	$new = New-Object System.Management.Automation.PSObject `
	| Add-Member -MemberType ScriptMethod -Name ToString -Value {("{0} object -> {1}" -f $this.PSObject.TypeNames[0], $this.Name)} -Force -PassThru `
	| Add-Member -MemberType NoteProperty -Name Name -Value $name -PassThru `
	| Add-Member -MemberType NoteProperty -Name MatchString -Value $matchString -PassThru `
	| Add-Member -MemberType NoteProperty -Name SubCats -Value @() -PassThru `

	$new.PSObject.TypeNames.Clear()
	$new.PSObject.TypeNames.Insert(0,'SDRSStorageCat')
	return $new
}
#endregion
#region SDRS Functions
function New-SDRS
{
	param `
	(
		[string] $folder = ( Throw "`$folder is a required parameter" ),
		$storageCats = ( Throw "`$storageCats is a required parameter" ),
		$maxVmsPerLun,
		$maxHotIOVmsPerLun,
		$maxMediumIOVmsPerLun,
		$minFreeGBPerLun
	)
	$new = New-Object System.Management.Automation.PSObject `
	| Add-Member -MemberType ScriptMethod -Name ToString -Value {("{0} object -> {1}" -f $this.PSObject.TypeNames[0], $this.Folder)} -Force -PassThru `
	| Add-Member -MemberType NoteProperty -Name Folder -Value $folder -PassThru `
	| Add-Member -MemberType NoteProperty -Name StorageCats -Value $storageCats -PassThru `
	| Add-Member -MemberType NoteProperty -Name CurrentLayout -Value $null -PassThru `
	| Add-Member -MemberType NoteProperty -Name NewLayout -Value $null -PassThru `
	| Add-Member -MemberType NoteProperty -Name MaxVmsPerLun -Value $maxVmsPerLun -PassThru `
	| Add-Member -MemberType NoteProperty -Name MaxHotIOVmsPerLun -Value $maxHotIOVmsPerLun -PassThru `
	| Add-Member -MemberType NoteProperty -Name MaxMediumIOVmsPerLun -Value $maxMediumIOVmsPerLun -PassThru `
	| Add-Member -MemberType NoteProperty -Name MinFreeGBPerLun -Value $minFreeGBPerLun -PassThru `

	$new.PSObject.TypeNames.Clear()
	$new.PSObject.TypeNames.Insert(0,'SDRS')
	return $new
}
function Prepare-SDRS
{
	param `
	(
		$sdrs = ( Throw "`$sdrs is a required parameter" )
	)
	$sdrs.CurrentLayout = New-SDRSLayout -name "CurrentLayout"`
		-storageCats $sdrs.StorageCats`
		-MaxVmsPerLun $sdrs.MaxVmsPerLun `
		-MaxHotIOVmsPerLun $sdrs.MaxHotIOVmsPerLun`
		-MaxMediumIOVmsPerLun $sdrs.MaxMediumIOVmsPerLun`
		-MinFreeGBPerLun $sdrs.MinFreeGBPerLun
	Get-CurrentLayout -layout $sdrs.CurrentLayout -folder $sdrs.Folder
	
	$sdrs.NewLayout = New-SDRSLayout -name "CurrentLayout"`
		-storageCats $sdrs.StorageCats`
		-MaxVmsPerLun $sdrs.MaxVmsPerLun `
		-MaxHotIOVmsPerLun $sdrs.MaxHotIOVmsPerLun`
		-MaxMediumIOVmsPerLun $sdrs.MaxMediumIOVmsPerLun`
		-MinFreeGBPerLun $sdrs.MinFreeGBPerLun
	Copy-Layout -source $sdrs.CurrentLayout -target $sdrs.NewLayout

	Set-SDRSLayoutIOTypes -layout $sdrs.CurrentLayout
	Optimize-SDRSLayout -source $sdrs.CurrentLayout -target $sdrs.NewLayout
}
#endregion
#region TestCode
Write-Host -ForegroundColor Blue "___________________________________ Starting ___________________________________"

Remove-Variable t1 -ErrorAction SilentlyContinue


#$c = @()
#$c = New-SDRSStorageCat -name P3 -matchString "^P3.*"
$c = New-SDRSStorageCat -name P2 -matchString "^P2.*"
#$c.SubCats += New-SDRSStorageCat -name P2A -matchString "^P2A.*"
#$c.SubCats += New-SDRSStorageCat -name P2B -matchString "^P2B.*"
#$c += New-SDRSStorageCat -name P3
$t1 = New-SDRS -folder "env-prod" -storageCats @($c) -MaxVmsPerLun 8 -MaxHotIOVmsPerLun 1 -MaxMediumIOVmsPerLun 2 -MinFreeGBPerLun 5
Prepare-SDRS -sdrs $t1
Print-SDRSLayoutByStorageCat $t1.NewLayout $c

#endregion
